ホームに戻る
関連 :
目次 :
仮想関数とは
基本クラスで定義され、派生クラスで再定義されるメンバ関数のこと。
基本クラスと異なる処理を実装する、または基本クラスでは概念のみを定義しておき、派生クラスで具体化するということができる。
C++に限らず、オブジェクト指向言語では一般的な概念。継承とともにオブジェクト指向の根幹をなす。

重要な前提 : 派生クラスと基本クラスのポインタ
基本クラスと派生クラスが存在する場合、 基本クラスのポインタは、その基本クラスから派生したすべてのクラスオブジェクトを指し示すことができる 。
// 基本クラス Vehicle
class Vehicle
{
public:
virtual void Accelarate();
:
}
// 派生クラス Car
class Car : public Vehicle
{
:
}
// Vehicle 型ポインタが Car の実体を指す
Vehicle* pOb_Vehicle;
Car Ob_Car;
pOb_Vehicle = &Ob_Car;
但し、(派生クラスオブジェクトを指す) 基本クラスポインタを用いてアクセスできるのは、基本クラスに存在するメンバのみである 。
(派生クラスで新たに追加したメンバは、基本クラスポインタを用いてアクセスすることはできない。キャストを用いて強引に行うことは不可能ではないが、推奨されない。)
仮想関数も例外ではないが、 基本クラスに存在する仮想関数を派生クラスで再定義しているため 、
(派生クラスオブジェクトを指す)基本クラスポインタを用いて仮想関数にアクセスすると、 派生クラスで再定義した仮想関数が正しく呼び出される 。
(基本クラスの関数を呼び出すわけではない。)
仮想関数の定義
// 基本クラス Vehicle
class Vehicle
{
public:
// 仮想関数 Accelarate() の定義 : 基本クラス
virtual void Accelarate()
{
:
}
:
}
// 派生クラス Car
class Car : public Vehicle
{
public:
// 仮想関数 Accelarate() の定義 : 派生クラス ⇒ virtual キーワードは不要
void Accelarate()
{
:
}
:
}
// 派生クラス Motorcycle
class Motorcycle : public Vehicle
{
public:
// 仮想関数 Accelarate() を定義しない ⇒ Vehicle の Accelarate() を使用
:
}
関数を再定義(上書き)することを 「オーバーライド (Override)」 と呼ぶ。
仮想関数のオーバーライドの際は、 基本クラスでの定義と引数の型・数、戻り値の型がすべて一致していなければならない 。
(オーバーロードとの混同に注意。オーバーロードは逆に、引数の型・数のいずれかが異なっていなければならない。)
なお、オーバーライド時に virtual キーワードは必須ではない。virtual キーワードを省いても関数の仮想性は引き継がれる。
仮想関数の用途 : 実行時ポリモーフィズム
上述の通り、基本クラスのポインタが派生クラスのオブジェクトを指すことができるため、以下のような実装が可能。
void Drive(int i)
{
Car Ob_Car; //< Car 型オブジェクト
Motorcycle Ob_Motorcycle; //< Motorcycle 型オブジェクト
Vehicle* pOb_Vehicle; //< 基本クラス Vehicle 型ポインタ
pOb_Vehicle = ( i == 0) ? &Ob_Car : &Ob_Motorcycle;
pOb_Vehicle->Accelarate();
}
ここで、i の値はプログラムを実行するまで確定しない(コンパイル時に決定できない)ため、pOb_Vehicle が Ob_Car と Ob_Motorcycle のいずれを指すかは実行時までわからない。
しかし、Accelarate() を基本クラスで仮想関数として定義しておけば、基本クラスポインタがいずれの派生クラスを指す場合でも、インタフェース(Accelarate() のコール)を変更する必要が無い。
(pOb_Vehicle が Ob_Car を指す場合は Car の、Ob_Motorcycle を指す場合は Motorcycle の Accelarate() が実行される。)
pOb_Vehicle はプログラムの実行時に、Car 、Motorcycle いずれのポインタとしても振舞うことができる(多態)。
この性質を「 実行時ポリモーフィズム 」と呼び、派生クラスとしても振舞えることから、(仮想関数を有する)基本クラスを「 ポリモーフィッククラス 」と呼ぶ。
尚、実行時ポリモーフィズムの実現に当たっては注意が必要。仮想デストラクタを参照のこと。
純粋仮想関数
基本クラスでは仮想関数に有意な処理を実装せずにインタフェース(引数・戻り値)のみを規定し、派生クラスで具体的な処理を実装することができる。
このような実体の無い仮想関数を 「純粋仮想関数(Pure Virtual Function)」 と呼ぶ。一般形は以下の通り。
ここで、
- retval-type : 戻り値の型
- func-name : 関数名
- parameter-list : 引数リスト
virtual retval-type func-name (parameter-list) = 0; //< 末尾に = 0 を付与することで純粋仮想化
純粋仮想関数を含むクラスを「抽象クラス(Abstract Class)」と呼ぶ 。
重要な点として、 抽象クラスは、自身のインスタンスを作成することができない 。継承されて初めて意味を持つクラスであり、継承され、純粋仮想関数がオーバーライドされることを前提としている。
但し、 抽象クラスへのポインタは作成できる 。(実行時ポリモーフィズムを実現するため。)
派生クラスで純粋仮想関数をオーバーライドしなかった場合は、その派生クラスも抽象クラスとなる(純粋仮想性が維持される)。
// 基本クラス Vehicle
class Vehicle
{
public:
// 純粋仮想関数 Accelarate() の定義
virtual void Accelarate() = 0;
:
}
// 派生クラス Car
class Car : public Vehicle
{
public:
// Accelarate() のオーバーライド
void Accelarate()
{
:
}
:
}
明示的な仮想関数オーバーライド (C++11)
基底クラスの仮想関数をオーバーライドする際に、(タイプミスなどで)意図に反する新しい仮想関数を作成してしまうことがある。
// 基底クラス
class C_Base
{
public:
virtual void some_func(float);
virtual bool on_error();
};
// 派生クラス
class C_Derived : C_Base
{
virtual void some_func(int); //< 基底クラスの some_func(float) をオーバーライドするつもりが、
//< 引数の型が異なっている
virtual bool on_eror(); //< 基底クラスの on_error() をオーバーライドするつもりが、
//< 関数名(シグネチャ)を誤っている
};
上記は派生クラスで基底クラスの仮想関数をオーバーライドしようとして、意図に反して新たな仮想関数を作成してしまった例である。
文法上は誤りではないためコンパイルエラーとはならず、間違いに気づくことができない。
C++11では override キーワードが導入され、仮想関数のオーバーライドであることを明示することができるようになった。
// 基底クラス
class C_Base
{
public:
virtual void some_func(float);
virtual bool on_error();
};
// 派生クラス
class C_Derived : C_Base
{
virtual void some_func(int) override; //< 不正 : 引数の型が異なっている
virtual bool on_eror() override; //< 不正 : 関数名(シグネチャ)を誤っている
};
上記は override 修飾により、仮想関数のオーバーライドであることを明示している。
これらが基底クラスのオーバーライド元と一致しない場合、コンパイルエラーとして検出できる。